package com.bcs.compiler;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.mozilla.javascript.FunctionNode;
import org.mozilla.javascript.Node;
import org.mozilla.javascript.Parser;
import org.mozilla.javascript.ScriptOrFnNode;
import org.mozilla.javascript.Token;
import org.mozilla.javascript.Node.Jump;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import sun.awt.GlobalCursorManager;

import com.bcs.moon.MoonException;
import com.bcs.moon.Scope;
import com.bcs.moon.UnimplementedException;
import com.bcs.moon.XCompile;

public class TargetAssemblerX86 extends XCompile implements Target {
	private final int ARGC_EBP_OFFSET= 24;
	private final int ARGV_EBP_OFFSET= 20;
	private final int CONTEXT_EBP_OFFSET= 8;
	private final int FRAME_EBP_OFFSET= -12;
	private final int RETURNVALUE_EBP_OFFSET= -16;
	
	//       argc
	//       argv
	//       this
	//       outer
	//       ctx
	//       ret value
	// (EBP) save ebp
	//       save esi
	//       save edi
	//       frame
	//       return value
	
	PrintWriter out;
	HashMap<String, List<Integer>> linkMap;
	HashMap<String, List<Integer>> globalLinkMap;
	HashMap<Double, Integer> doubleConstantMap;
	HashMap<String, Integer> stringConstantMap;
	HashMap<String, Integer> cstringConstantMap;
	List<Integer> earlyExitList;
	List<Integer> functionMap;
	int fullCompiledOffset;
	ByteArrayOutputStream codeStream;
	int maxCallFrame;
	ByteArrayOutputStream fullCode;
	Map<Node, List<Integer>> m_References;
	Map<Node, Integer> m_Targets;
	List<CodeRef> codeReferences;
	List<CodeRef> allCodeReferences;
	Map<String, Integer> allCodeDefinitions;
	List<Integer> entryPoints, coreEntryPoints;
	List<String> imageNames;
	List<Integer> imageOffsets;

	public TargetAssemblerX86() {
		super();
		target = this;
	}
	
	public void initializeTarget() {
		globalLinkMap = new HashMap<String, List<Integer>>();
		linkMap = new HashMap<String, List<Integer>>();
		functionMap = new ArrayList<Integer>();
		doubleConstantMap = new HashMap<Double, Integer>();
		stringConstantMap = new HashMap<String, Integer>();
		cstringConstantMap = new HashMap<String, Integer>();
		imageNames = new ArrayList<String>();
		imageOffsets = new ArrayList<Integer>();
		fullCompiledOffset = 0;
		codeStream = null;
		maxCallFrame = 4;
		out = null;
		fullCode = new ByteArrayOutputStream();
		allCodeReferences = new ArrayList<CodeRef>();
		allCodeDefinitions = new HashMap<String, Integer>();
		entryPoints = new ArrayList<Integer>();
		coreEntryPoints = new ArrayList<Integer>();
	}

	private ByteArrayOutputStream body;
	private HashMap<String, List<Integer>> bodyLinkMap;
	
	private void enterCompilation(String name, Scope priorScope) {
		reset(priorScope);
		body = new ByteArrayOutputStream();
		StringWriter bodyString = new StringWriter();
		PrintWriter bodyWriter = new PrintWriter(bodyString);
		bodyLinkMap = new HashMap<String, List<Integer>>();

		allCodeDefinitions.put(name, fullCompiledOffset);
		
		codeStream =  body;
		out = bodyWriter;
		linkMap = bodyLinkMap;
		
		m_References = new HashMap<Node, List<Integer>>();
		m_Targets = new HashMap<Node, Integer>();
		codeReferences = new ArrayList<CodeRef>();
		
		earlyExitList = new ArrayList<Integer>(); 
	}
	
	private void finishCompilation(int paramCount, boolean scriptLevel, boolean isCore) {
//		System.out.println(isCore + "," + currentScript);
		
		int exitPos = codeStream.size();
		List<Integer> bodyExits = earlyExitList;
		earlyExitList = null;
		
		movl_off_ebp_eax(CONTEXT_EBP_OFFSET);
		movl_eax_off_esp(0);
		movl_off_ebp_eax(FRAME_EBP_OFFSET);
		movl_eax_off_esp(4);
		callIntrinsic("leaveFrame", 4);

		// pull return value
		movl_off_ebp_eax(RETURNVALUE_EBP_OFFSET);
		
		int localsSize = 3;
		int frameSize = (maxCallFrame + localsSize) * 4;
		if ((frameSize & 15) != 0)
			frameSize += 16 - (frameSize & 15);
		
		if (frameSize >= 128)
			throw new UnimplementedException();
		else if (frameSize > 0) {
			encodeSource("addl\t$" + frameSize + ", %esp");
			encode(0x83);
			encode(0xc4);
			encode(frameSize);
		}

		encodeSource("pop\t%edi");
		encode(0x5f);
		encodeSource("pop\t%esi");
		encode(0x5e);

		encodeSource("pop\t%ebp");
		encode(0x5d);
		encodeSource("ret");
		encode(0xc3);

		ByteArrayOutputStream prologue = new ByteArrayOutputStream();
		StringWriter prologueString = new StringWriter();
		PrintWriter prologueWriter = new PrintWriter(prologueString);
		HashMap<String, List<Integer>> prologueLinkMap = new HashMap<String, List<Integer>>();

		codeStream =  prologue;
		out = prologueWriter;
		linkMap = prologueLinkMap;

		encodeSource("// entry");
		encodeSource("push\t%ebp");
		encode(0x55);

		encodeSource("movl\t%esp, %ebp");
		encode(0x89);
		encode(0xe5);
		
		encodeSource("push\t%esi");
		encode(0x56);
		encodeSource("push\t%edi");
		encode(0x57);
		
		if (frameSize >= 128)
			throw new UnimplementedException();
		else if (frameSize > 0) {
			encodeSource("subl\t$" + frameSize + ", %esp");
			encode(0x83);
			encode(0xec);
			encode(frameSize);
		}

		// clear return value
		movl_imm4_off_ebp(0, RETURNVALUE_EBP_OFFSET);

		// get context
		movl_off_ebp_eax(8);
		// establish slots
		movl_off_eax_esi(0);

		// get stack frame
		movl_off_ebp_eax(8);
		movl_eax_off_esp(0);
		movl_off_ebp_eax(12);
		movl_eax_off_esp(4);
		movl_imm4_off_esp(currentFrame.nextSlot, 8);
		movl_imm4_off_esp(maxArgs, 12);
		
		if (enableDebugging && !isCore) {
			loadEAX(new ValueStringConstant(currentScript));
			movl_eax_off_esp(16);
			loadEAX(new ValueStringConstant(getDebugFrame()));
			movl_eax_off_esp(20);
			callIntrinsic("enterStackDebug", 6);
		} else {
			callIntrinsic("enterStack", 4);
		}
		movl_eax_off_ebp(FRAME_EBP_OFFSET);

		// establish locals
		movl_off_eax_edi(0);
		
		if (paramCount > 0) {
			movl_off_ebp_eax(FRAME_EBP_OFFSET);
			movl_eax_off_esp(0);
			movl_imm4_off_esp(paramCount, 4);
			movl_off_ebp_eax(ARGV_EBP_OFFSET);
			movl_eax_off_esp(8);
			movl_off_ebp_eax(ARGC_EBP_OFFSET);
			movl_eax_off_esp(12);
			callIntrinsic("copyParams", 4);
		}
		
		codeStream = null;
		out = null;
		linkMap = null;
			
		//System.out.print(prologueString.toString());
		//System.out.print(bodyString.toString());

		byte[] prologueData = prologue.toByteArray();
		byte[] bodyData = body.toByteArray();
	
		for (int idx = 0; idx < bodyExits.size(); idx++) {
			int pos = bodyExits.get(idx);
			int delta = exitPos - pos;
			bodyData[pos - 4] = (byte) (delta & 0xff);
			bodyData[pos - 3] = (byte) ((delta >> 8) & 0xff);
			bodyData[pos - 2] = (byte) ((delta >> 16) & 0xff);
			bodyData[pos - 1] = (byte) ((delta >> 24) & 0xff);
		}
		
		fixReferences(bodyData);
		mergeLinkMap(prologueLinkMap, fullCompiledOffset);
		mergeLinkMap(bodyLinkMap, fullCompiledOffset + prologueData.length);

		m_References = null;
		m_Targets = null;

		for (CodeRef ref : codeReferences) {
			ref.offset += fullCompiledOffset + prologueData.length;
			allCodeReferences.add(ref);
		}
		
		if (scriptLevel) {
			if (isCore)
				coreEntryPoints.add(fullCompiledOffset);
			else
				entryPoints.add(fullCompiledOffset);
		}
		
		fullCompiledOffset += prologueData.length + bodyData.length;
		/*
		System.out.println();
		System.out.println("prologue size: "+prologueData.length);
		for (int idx = 0; idx < prologueData.length; idx++)
			System.out.print("0x" + Integer.toHexString(prologueData[idx] & 0xff) + ",");
		for (int idx = 0; idx < bodyData.length; idx++)
			System.out.print("0x" + Integer.toHexString(bodyData[idx] & 0xff) + ",");
		System.out.println();
*/
		fullCode.write(prologueData, 0, prologueData.length);
		fullCode.write(bodyData, 0, bodyData.length);
		/*
		if (enableDebugging)
			out.println("jsframe *frame=enterStackDebug(cx, outer, " + currentFrame.nextSlot + "," + maxArgs + "," + encodeString(currentScript) + ");");
		else
			out.println("jsframe *frame=enterStack(cx, outer, " + currentFrame.nextSlot + "," + maxArgs + ");");
		out.println("jsvalue *slot = frame->slots;");
		out.println("jsvalue rv;");
		for (int idx = 0; idx < currentFrame.nextSlot; idx++) {
			if (idx < paramCount) {
				out.println("slot[" + idx + "] = argc > " + idx + " ? args[" + idx + "] : JSVAL_VOID;");
			}
		}
		out.println("jsvalue *callFrame = frame->callFrame;");
		out.print(localBinderString.toString());
		out.print(w.toString());
		
//		if (scriptLevel) {
			out.println("leaveFrame(cx, frame);");
			out.println("return JSVAL_VOID;");
			
//		}
		
		out.println("}");
		*/

	}
	
	public void compileFunction(String name, ScriptOrFnNode node, boolean scriptLevel, boolean isCore, Scope priorScope) {
	
		int subCount = node.getFunctionCount();

		internalFunctionNames= new ArrayList<String>();
		enterCompilation(name, priorScope);

		int paramCount = 0;
		if (node instanceof FunctionNode) {
			FunctionNode fn = (FunctionNode) node;
			paramCount = fn.getParamCount();
			fn.flattenSymbolTable(false);
			String[] names = fn.getParamAndVarNames();
			for (int idx = 0; idx < paramCount; idx++) {
				currentFrame.nextSlot++;
				currentScope.addLocal(names[idx], idx);
			}
		}
		
		if (scriptLevel) {
			for (int idx = 0; idx < subCount; idx++) {
				FunctionNode fn = node.getFunctionNode(idx);
				String fnname = fn.getFunctionName();
				if (fnname.equals("")) {
					fnname = "function" + nextFunctionId++;
				} else {
				}
				internalFunctionNames.add(fnname);
			}
		} else {
			for (int idx = 0; idx < subCount; idx++) {
				FunctionNode fn = node.getFunctionNode(idx);
				String fnname = fn.getFunctionName();
				String internalFunctionName = "function" + nextFunctionId++;
				if (!fnname.equals("")) {
					int fnIdx = currentFrame.nextSlot++;
					currentScope.addLocal(fnname, fnIdx);
				}
				internalFunctionNames.add(internalFunctionName);
			}
		}
		
		
		compileCode(node, scriptLevel);

		finishCompilation(paramCount, scriptLevel, isCore);
		
		ArrayList<String> functionNames = internalFunctionNames;
		Scope fnScope = null;
		if (!scriptLevel)
			fnScope = currentScope;
		for (int idx = 0; idx < subCount; idx++) {
			String fnname = functionNames.get(idx);
			FunctionNode fn = node.getFunctionNode(idx);

			if (scriptLevel) {
				String declName = fn.getFunctionName();
				if (declName != null && declName.length() > 0) {
					Object gv = getGlobalValue(declName);
					if (gv instanceof ValueGlobalSlot) {
						ValueGlobalSlot vgs = (ValueGlobalSlot) gv;
						functionMap.add(vgs.slot);
						functionMap.add(fullCompiledOffset);
					}
				}
			}
			
			compileFunction("code_" + fnname, fn, false, isCore, fnScope);
			
			constantDecls.add("static jsvalue code_" + fnname + "(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc);");
		}
	}

	public void compileCode(Node node, boolean scriptLevel) {
		for (Node n = node.getFirstChild(); n != null; n = n.getNext()) {
			if (n.getLineno() != -1) {
//				System.out.println(n.getLineno());
				out.println();
				out.print("// line " + n.getLineno());
				currentLine = n.getLineno();

				if (enableDebugging && inUserScript()) {
					int breakpointIdx = breakpointScripts.size();
					breakpointScripts.add(currentScript);
					breakpointLines.add(currentLine);
					
					movl_off_ebp_eax(CONTEXT_EBP_OFFSET);
					movl_eax_off_esp(0);
					movl_imm4_off_esp(breakpointIdx, 4);
					movl_imm4_off_esp(currentLine, 8);
					
					callIntrinsic("jsDebugCheckBreakpoint", 3);
				}
			}
			int t = n.getType();
			if (t == Token.VAR) {
				compileVar(n, scriptLevel);
			} else if (t == Token.EXPR_RESULT || t == Token.EXPR_VOID) {
				Node expr = n.getFirstChild();
				boolean handled= false;
				
				if (expr.getType() == Token.SETNAME) {
					Node child = expr.getFirstChild();
					Integer localSlot = currentScope.getName(child.getString());
	
					if (localSlot != null) {
						compileExpression(child.getNext(), localSlot);
						handled = true;
					} else if (canDirectCompile(child.getNext())) {
						assign(nameToValue(child.getString()), directValue(child.getNext()));
						handled = true;
					}
				} 
				if (!handled) {
					int slot = currentFrame.getTempSlot();
					compileExpression(n.getFirstChild(), slot);
					currentFrame.releaseTempSlot(slot);
				}
			} else if (t == Token.FUNCTION) {

			} else if (t == Token.BLOCK) {
				compileCode(n, scriptLevel);
			} else if (t == Token.RETURN) {
				if (n.getFirstChild() != null) {
					int slot = currentFrame.getTempSlot();
					compileExpression(n.getFirstChild(), slot);
					
					loadEAX(new ValueLocal(slot));
					movl_eax_off_ebp(RETURNVALUE_EBP_OFFSET); //?
					currentFrame.releaseTempSlot(slot);
				}
				encodeSource("jmp _fnexit");
				encode(0xe9);
				encode(0);
				encode(0);
				encode(0);
				encode(0);
				
				earlyExitList.add(codeStream.size());					
			} else if (t == Token.IFEQ) {
				int slot = currentFrame.getTempSlot();
				compileExpression(n.getFirstChild(), slot);
				currentFrame.releaseTempSlot(slot);
				loadEAX(new ValueLocal(slot));
				movl_eax_off_esp(0);
				callIntrinsic("ifeq", 1);
				cmp_imm4_eax(0);
				encodeSource("jne " + targetLabel(((Jump) n).target));
				encode(0x0f);
				encode(0x85);
				encode(0);
				encode(0);
				encode(0);
				encode(0);
				addReference( ((Jump) n).target);
//				out.println("if (ifeq(" + localValue(slot) +")) goto " + targetLabel( ((Jump) n).target) + ";");
			} else if (t == Token.IFNE) {
				int slot = currentFrame.getTempSlot();
				compileExpression(n.getFirstChild(), slot);
				currentFrame.releaseTempSlot(slot);	
				loadEAX(new ValueLocal(slot));
				movl_eax_off_esp(0);
				callIntrinsic("ifne", 1);
				cmp_imm4_eax(0);
				encodeSource("jne " + targetLabel(((Jump) n).target));
				encode(0x0f);
				encode(0x85);
				encode(0);
				encode(0);
				encode(0);
				encode(0);
				addReference( ((Jump) n).target);
			} else if (t == Token.EMPTY) {
				out.println("//empty");

			} else if (t == Token.LOOP) {
				compileCode(n, scriptLevel);
			} else if (t == Token.GOTO) {
				out.println("goto " + targetLabel(((Jump) n).target) + ";");
				encodeSource("jmp _fnexit");
				encode(0xe9);
				encode(0);
				encode(0);
				encode(0);
				encode(0);
				addReference( ((Jump) n).target);
			} else if (t == Token.TARGET) {
				addTarget(n);
				out.println(targetLabel(n) + ":;");
			} else if (t == Token.DEBUGGER) {
				movl_off_ebp_eax(8);
				movl_eax_off_esp(0);
				callIntrinsic("trace", 1);
			} else {
				System.out.println("Unknown: " + n);
				throw new MoonException(n.getLineno(), "Unimplemented javascript feature");
			}
		}
	}
	
	private void addReference(Node n) {
		List<Integer> refs = m_References.get(n);
		if (refs == null) {
			refs = new ArrayList<Integer>();
			m_References.put(n, refs);
		}
		refs.add(codeStream.size());
	}
	
	private void addTarget(Node n) {
		m_Targets.put(n, codeStream.size());
	}
	
	private void fixReferences(byte[] code) {
		for (Entry<Node, List<Integer>> e : m_References.entrySet()) {
			int target = m_Targets.get(e.getKey());
			for (Integer ref : e.getValue()) {
				int off = ref;
				int delta = target - off;
				code[off - 4] = (byte) (delta & 0xff);
				code[off - 3] = (byte) ((delta >> 8) & 0xff);
				code[off - 2] = (byte) ((delta >> 16) & 0xff);
				code[off - 1] = (byte) ((delta >> 24) & 0xff);
			}
			
		}
	}
	
	@Override
	public void handleCompile(String targetFile, String mainFunction) {
		for (int idx = 0; idx < scriptNodes.size(); idx++) {
			currentScript = scriptNames.get(idx);
			System.out.println(currentScript);
			boolean isCore = false;
			if (currentScript.startsWith("icm"))
				isCore = true;
			compileFunction("mainFunction" + idx, scriptNodes.get(idx), true, isCore, null);
		}
		for (int idx = 0; idx < functionNodes.size(); idx++) {
			currentScript = functionScripts.get(idx);
			compileFunction("code_" + functionNames.get(idx), functionNodes.get(idx), false, false, null);
		}
	}

	protected void compileForm(String scriptName, Object obj, Element rootDict, boolean isControl) {
		NodeList children = rootDict.getChildNodes();
		int count = children.getLength();
		int arrSlot = -1;

		int mode = 0;
		String key = null;
		Element controls = null;
		
		String ctlName;
		if (isControl)
			ctlName = getControlName(rootDict);
		else
			ctlName = "_form";
		
		for (int idx = 0; idx < count; idx++) {
			org.w3c.dom.Node n = children.item(idx);
			if (n instanceof Element) {
				Element el = (Element) n;
				if (mode == 0) {
					String nodeName = el.getNodeName();
					if (!nodeName.equals("key"))
						throw new RuntimeException("Malformed property list");
					key = el.getTextContent();
					mode = 1;
				} else {
					String valueType = el.getNodeName();
					if (valueType.equals("false")) {
						target.invokeSet(obj, key, ValueBooleanConstant.FALSE);
					} else if (valueType.equals("true")) {
						target.invokeSet(obj, key, ValueBooleanConstant.TRUE);
					} else if (valueType.equals("integer")) {
						target.invokeSet(obj, key, new ValueNumberConstant(Integer.parseInt(el.getTextContent())));
					} else if (valueType.equals("string")) {
						if (Character.isUpperCase(key.charAt(0))) {
							String fnscript = el.getTextContent();
							if (fnscript.trim().length() > 0) {
								String fnname = "_eventHandler" + gNextEventHandlerId++;
								parseFunction(scriptName + "/" + ctlName + "/" + key, fnscript, "code_" + fnname);
								if (hasError)
									return;
								int tempSlot = currentFrame.getTempSlot();
								assignClosure(new ValueLocal(tempSlot), fnname);
								target.invokeSet(obj, key, new ValueLocal(tempSlot));
								currentFrame.releaseTempSlot(tempSlot);
							}
						} else {
							target.invokeSet(obj, key, new ValueStringConstant(el.getTextContent()));
						}
					} else if (valueType.equals("array") && key.equals("controls")) {
						controls = el;
						
						arrSlot = currentFrame.getTempSlot();
						target.callSimpleFunction(arrSlot, "jsnewarray");
//						writer.println("jsset(cx," + obj + "," + hash(key) + "," + encodeString(key) + ",ctlArr=jsnewarray(cx));");
						target.invokeSet(obj, key, new ValueLocal(arrSlot));
					}
					mode = 0;
				}
			}
		}
		
		if (!isControl && controls != null) {
			children = controls.getChildNodes();
			count = children.getLength();

			for (int idx = 0; idx < count; idx++) {
				org.w3c.dom.Node n = children.item(idx);
				if (n instanceof Element) {
					Element el = (Element) n;

					int ctlSlot = currentFrame.getTempSlot();
					target.callSimpleFunction(ctlSlot, "jsnewobject");
					target.callVoidRuntime("jspush", new ValueLocal(arrSlot), new ValueLocal(ctlSlot));
					
//					writer.println("jspush(cx, ctlArr, ctl=jsnewobject(cx));");
					compileForm(scriptName, new ValueLocal(ctlSlot),  el, true);
					currentFrame.releaseTempSlot(ctlSlot);
					if (hasError)
						return;
				}
			}
		}
		if (arrSlot != -1)
			currentFrame.releaseTempSlot(arrSlot);
	}

	@Override
	public void parseForm(String scriptPath, String scriptName, String formName) {
		try {
			currentScript = scriptName;
			Document doc = builder.parse(new File(scriptPath));
			Element el = doc.getDocumentElement();
			Element rootDict = (Element) el.getElementsByTagName("dict").item(0);
			Integer formKind = (Integer) getPropertyDictionaryEntry(rootDict, "kind");

			enterCompilation(formName, null);
			
			int tempSlot = currentFrame.getTempSlot();
			target.callSimpleFunction(tempSlot, "jsnewobject");

			compileForm(scriptName, new ValueLocal(tempSlot), rootDict, false);

			String factory;
			if (formKind == null || formKind == 0)
				factory = "DefineWindow";
			else if (formKind == 1)
				factory = "DefineSheet";
			else if (formKind == 2)
				factory = "DefineControl";
			else if (formKind == 3)
				factory = "DefinePanel";
			else
				throw new RuntimeException("Unimplemented form kind");
			
			int fnSlot = currentFrame.getTempSlot();
			target.invokeGet(new ValueLocal(fnSlot), new ValueGlobalSlot(9), factory);

			int globId = nextGlobalId++;
			defineGlobal(formName, globId);

			ArrayList<Object> params = new ArrayList<Object>();
			params.add(new ValueLocal(tempSlot));
			target.invokeInvoke(new ValueGlobalSlot(globId), new ValueLocal(fnSlot), ValueNullConstant.INSTANCE, params);
//			formWriter.println("callFrame[0] = slot[2];");
//			formWriter.println(valueToString(getGlobalValue(formName)) + " = jsinvoke(cx, slot[1], cx->slots[GLOBAL_UI], &obj, 1);");

			currentFrame.releaseTempSlot(tempSlot);
			currentFrame.releaseTempSlot(fnSlot);

			finishCompilation(0, true, false);
		} catch (IOException ex) {
			throw new RuntimeException(ex);
		} catch (SAXException ex) {
			throw new RuntimeException(ex);
		}
	}

	@Override
	public Object parseFunction(String scriptName, String script, String fnname) {
		try {
			currentScript = scriptName;
			Parser parser = new Parser(compilerEnv, errorReporter);
	
			ScriptOrFnNode node = parser.parse(
					"function __int_fn() {" + script + "}", "src://none", 1);
			
			functionNodes.add(node.getFunctionNode(0));
			functionNames.add(fnname);
			functionScripts.add(scriptName);
			return null;
		} catch (MoonException ex) {
			hasError = true;
			errorLine = ex.line;
			errorScript = scriptName;
			errorMessage = ex.description;
			return null;
		} catch (Exception ex) {
			hasError = true;
			errorLine = -1;
			errorScript = scriptName;
			errorMessage = "An unexpected error occurred: " + ex.toString();	
			return null;
		}
	}
	
	private boolean isCoreScript(String path) {
		int slashIdx = path.lastIndexOf('/');
		if (slashIdx >= 0)
			path = path.substring(slashIdx + 1);
		return path.startsWith("icm");
	}

	public byte[] getFullCode() {
		try {
			for (int idx = 0; idx < scriptNodes.size(); idx++) {
				currentScript = scriptNames.get(idx);
				boolean isCore = isCoreScript(currentScript);
				compileFunction("mainFunction" + idx, scriptNodes.get(idx), true, isCore, null);
			}
			
			for (int idx = 0; idx < functionNodes.size(); idx++) {
				String name = functionNames.get(idx);
				ScriptOrFnNode node = (ScriptOrFnNode) functionNodes.get(idx);
				currentScript = functionScripts.get(idx);
				compileFunction(name, node, false, false, null);
			}

		} catch (MoonException ex) {
			hasError = true;
			errorLine = ex.line;
			errorScript = currentScript;
			errorMessage = ex.description;
			return null;
		} catch (Exception ex) {
			hasError = true;
			errorLine = -1;
			errorScript = currentScript;
			errorMessage = "An unexpected error occurred: " + ex.toString();	
			return null;
		}

		functionNames.clear();
		functionNodes.clear();
		functionScripts.clear();
		
		byte[] code = fullCode.toByteArray();
/*
		for (Entry<String, Integer> e : allCodeDefinitions.entrySet()) {
			System.out.println(e.getKey() + " @ " + e.getValue());
		}
*/
		for (CodeRef ref : allCodeReferences) {
			int off = allCodeDefinitions.get(ref.name);
			int pos = ref.offset;
			
			code[pos - 4] = (byte) (off & 0xff);
			code[pos - 3] = (byte) ((off >> 8) & 0xff);
			code[pos - 2] = (byte) ((off >> 16) & 0xff);
			code[pos - 1] = (byte) ((off >> 24) & 0xff);
		}
		
		return code;
	}
	
	private boolean isImmediateValue(Object v) {
		if (v instanceof ValueBooleanConstant)
			return true;
		if (v instanceof ValueUndefinedConstant)
			return true;
		if (v instanceof ValueNullConstant)
			return true;
		if (v instanceof ValueNumberConstant) {
			ValueNumberConstant vnc = (ValueNumberConstant) v;
			double dv = vnc.value;
			if (dv == Math.floor(dv) && dv >= -32767 && dv <= 32767)
				return true;
			return false;
		}
		return false;
	}
/*	
	private boolean canDirectCompile(Object v) {
		if (v instanceof ValueBooleanConstant)
			return true;
		if (v instanceof ValueNumberConstant) {
			ValueNumberConstant vnc = (ValueNumberConstant) v;
			double dv = vnc.value;
			if (dv == Math.floor(dv) && dv >= -32767 && dv <= 32767)
				return true;
			return false;
		}
		return false;
	}
	*/
	private int immediateValue(Object v) {
		if (v instanceof ValueBooleanConstant) {
			if (((ValueBooleanConstant) v).value)
				return 14;
			else
				return 6;
		}
		if (v instanceof ValueNumberConstant) {
			double dv = ((ValueNumberConstant) v).value;
			int nv = (int) dv;
			return (nv * 2) | 1;
		}
		if (v instanceof ValueUndefinedConstant) {
			return 0;
		}
		if (v instanceof ValueNullConstant) {
			return 0x80000001;
		}
		return -1;
	}
		
	public void assignEAX(Object target) {
		if (target instanceof ValueLocal) {
			int off = 4 * ((ValueLocal) target).slot;
			if (off == 0) {
				encodeSource("movl\t%eax, (%edi)");
				encode(0x89);
				encode(0x07);
			} else if (off < 128) {
				encodeSource("movl\t%eax, " + off + "(%edi)");
				encode(0x89);
				encode(0x47);
				encode(off);
			} else {
				encodeSource("movl\t" + "%eax, " + off + "(%edi)");
				encode(0x89);
				encode(0x87);
				encode(off);
				encode(off >>> 8);
				encode(off >>> 16);
				encode(off >>> 24);
			}
		} else if (target instanceof ValueGlobalSlot) {
			int off = 4 * ((ValueGlobalSlot) target).slot;
			if (off == 0) {
				encodeSource("movl\t%eax, (%esi)");
				encode(0x89);
				encode(0x06);
			} else if (off < 128) {
				encodeSource("movl\t%eax, " + off + "(%esi)");
				encode(0x89);
				encode(0x46);
				encode(off);
			} else {
				encodeSource("movl\t" + "%eax, " + off + "(%esi)");
				encode(0x89);
				encode(0x86);
				encode(off);
				encode(off >>> 8);
				encode(off >>> 16);
				encode(off >>> 24);
			}
		} else {
			encodeSource("mov\t%eax,???");
		}
	}

	public void cmp_imm4_eax(int nv) {
		encodeSource("cmp\t$" +nv + ", %eax");
		encode(0x3d);
		encode(nv);
		encode(nv >>> 8);
		encode(nv >>> 16);
		encode(nv >>> 24);
	}
	
	public void loadEAX(Object v) {
		 if (v instanceof ValueNumberConstant) {
			Double dv = ((ValueNumberConstant)v).value;
			Integer slot = doubleConstantMap.get(dv);
			if (slot == null) {
				slot = nextGlobalId++;
				doubleConstantMap.put(dv, slot);
			}
			v = new ValueGlobalSlot(slot);
		 } else if (v instanceof ValueStringConstant) {
			String sv = ((ValueStringConstant)v).value;
			Integer slot = stringConstantMap.get(sv);
			if (slot == null) {
				slot = nextGlobalId++;
				stringConstantMap.put(sv, slot);
			}
			v = new ValueGlobalSlot(slot);
		 }

		 if (isImmediateValue(v)) {
			 movl_imm4_eax(immediateValue(v));
			 return;
		 }
		 if (v instanceof ValueThis) {
			 movl_off_ebp_eax(16);
			 return;
		 }
		 
		if (v instanceof ValueGlobalSlot) {
			int off = 4 * ((ValueGlobalSlot) v).slot;
			if (off == 0) {
				encodeSource("movl\t(%esi),%eax");
				encode(0x8b);
				encode(0x06);
			} else if (off < 128) {
				encodeSource("movl\t" + off + "(%esi),%eax");
				encode(0x8b);
				encode(0x46);
				encode(off);
			} else {
				encodeSource("movl\t" + off + "(%esi),%eax");
				encode(0x8b);
				encode(0x86);
				encode(off);
				encode(off >> 8);
				encode(off >> 16);
				encode(off >> 24);
			}
		} else if (v instanceof ValueLocal) {
			int off = 4 * ((ValueLocal) v).slot;
			if (off == 0) {
				encodeSource("movl\t(%edi),%eax");
				encode(0x8b);
				encode(0x07);
			} else if (off < 128) {
				encodeSource("movl\t" + off + "(%edi),%eax");
				encode(0x8b);
				encode(0x47);
				encode(off);
			} else {
				encodeSource("movl\t" + off + "(%edi),%eax");
				encode(0x8b);
				encode(0x87);
				encode(off);
				encode(off >> 8);
				encode(off >> 16);
				encode(off >> 24);
			}
		} else if (v instanceof ValueScopeLocal) {
			ValueScopeLocal vsl = (ValueScopeLocal) v;
			int off = 4 * vsl.slot;
			
			movl_off_ebp_eax(FRAME_EBP_OFFSET);
			for (int o = 0; o < vsl.depth; o++)
				movl_off_eax_eax(8);
			movl_off_eax_eax(0);
			movl_off_eax_eax(off);
		} else if (v instanceof ValueNullConstant) {
			movl_imm4_eax(0x80000001);
		} else {
			encodeSource("movl\t???,%eax");
			throw new UnimplementedException();
		}
	}
	
	private void encodeSource(String v) {
		out.println();
		out.print(v + " // ");
	}
	
	private void encode(int v) {
		out.print(" " + Integer.toHexString(v & 0xff));
		codeStream.write(v & 0xff);
	}
	
	public void assign(Object target, Object v) {
		if (target instanceof ValueLocal) {
			ValueLocal vlt = (ValueLocal) target;
			if (isImmediateValue(v)) {
				int nv = immediateValue(v);
				int off = vlt.slot * 4;
				if (off == 0) {
					encodeSource("movl\t$"+ nv + ", (%edi)");
					encode(0xc7);
					encode(0x07);
					encode(nv);
					encode(nv >>> 8);
					encode(nv >>> 16);
					encode(nv >>> 24);
				} else if (off < 128) {
					encodeSource("movl\t$"+ nv + ", " + off + "(%edi)");
					encode(0xc7);
					encode(0x47);
					encode(off);
					encode(nv);
					encode(nv >>> 8);
					encode(nv >>> 16);
					encode(nv >>> 24);
				} else {
					encodeSource("movl\t$"+ nv + ", " + off + "(%edi)");
					encode(0xc7);
					encode(0x87);
					encode(off);
					encode(off >>> 8);
					encode(off >>> 16);
					encode(off >>> 24);
					encode(nv);
					encode(nv >>> 8);
					encode(nv >>> 16);
					encode(nv >>> 24);
				}
			}
			else {
				loadEAX(v);
				assignEAX(target);
			}
		} else if (target instanceof ValueGlobalSlot) {
			ValueGlobalSlot vlt = (ValueGlobalSlot) target;
			if (isImmediateValue(v)) {
				int nv = immediateValue(v);
				int off = vlt.slot * 4;
				if (off == 0) {
					encodeSource("movl\t$"+ nv + ", (%esi)");
					encode(0xc7);
					encode(0x06);
					encode(nv);
					encode(nv >>> 8);
					encode(nv >>> 16);
					encode(nv >>> 24);
				} else if (off < 128) {
					encodeSource("movl\t$"+ nv + ", " + off + "(%esi)");
					encode(0xc7);
					encode(0x46);
					encode(off);
					encode(nv);
					encode(nv >>> 8);
					encode(nv >>> 16);
					encode(nv >>> 24);
				} else {
					encodeSource("movl\t$"+ nv + ", " + off + "(%esi)");
					encode(0xc7);
					encode(0x46);
					encode(off);
					encode(off >>> 8);
					encode(off >>> 16);
					encode(off >>> 24);
					encode(nv);
					encode(nv >>> 8);
					encode(nv >>> 16);
					encode(nv >>> 24);
				}
			}
			else {
				loadEAX(v);
				assignEAX(target);
			}
		} else {
			encodeSource("mov\t???,???");
			throw new UnimplementedException();
		}
	}

	public void assignLocal(int slot, Object v) {
		assign(new ValueLocal(slot), v);
	}

	private void movl_off_ebp_eax(int offset) {
		encodeSource("movl\t"+ offset + "(%ebp), %eax");
		if (offset < 128) {
			encode(0x8b);
			encode(0x45);
			encode(offset);
		} else {
			encode(0x8b);
			encode(0x85);
			encode(offset);
			encode(offset >>> 8);
			encode(offset >>> 16);
			encode(offset >>> 24);
		}
	}

	private void movl_off_eax_edx(int offset) {
		encodeSource("movl\t"+ offset + "(%eax), %edx");
		if (offset < 128) {
			encode(0x8b);
			encode(0x50);
			encode(offset);
		} else {
			encode(0x8b);
			encode(0x90);
			encode(offset);
			encode(offset >>> 8);
			encode(offset >>> 16);
			encode(offset >>> 24);
		}
	}
	
	private void movl_off_eax_eax(int offset) {
		encodeSource("movl\t"+ offset + "(%eax), %eax");
		if (offset == 0) {
			encode(0x8b);
			encode(0x0);
		} else if (offset >= -128 && offset < 128) {
			encode(0x8b);
			encode(0x40);
			encode(offset);
		} else {
			encode(0x8b);
			encode(0x80);
			encode(offset);
			encode(offset >> 8);
			encode(offset >> 16);
			encode(offset >> 24);
			
		}
	}
	
	private void movl_off_eax_esi(int offset) {
		encodeSource("movl\t"+ offset + "(%eax), %esi");
		if (offset < 128) {
			encode(0x8b);
			encode(0x70);
			encode(offset);
		} else {
			encode(0x8b);
			encode(0xb0);
			encode(offset);
			encode(offset >> 8);
			encode(offset >> 16);
			encode(offset >> 24);

		}
	}

	private void movl_off_eax_edi(int offset) {
		encodeSource("movl\t"+ offset + "(%eax), %edi");
		if (offset < 128) {
			encode(0x8b);
			encode(0x78);
			encode(offset);
		} else {
			encode(0x8b);
			encode(0xb8);
			encode(offset);
			encode(offset >> 8);
			encode(offset >> 16);
			encode(offset >> 24);
		}
	}

	private void movl_imm4_eax(int nv) {
		encodeSource("movl\t$"+ nv + ", %eax");
			encode(0xb8);
			encode(nv);
			encode(nv >>> 8);
			encode(nv >>> 16);
			encode(nv >>> 24);
	}

	private void movl_imm4_off_esp(int nv, int offset) {
		encodeSource("movl\t$"+ nv + ", " + offset + "(%esp)");
		if (offset == 0) {
			encode(0xc7);
			encode(0x04);
			encode(0x24);
			encode(nv);
			encode(nv >>> 8);
			encode(nv >>> 16);
			encode(nv >>> 24);
		} else if (offset < 128) {
			encode(0xc7);
			encode(0x44);
			encode(0x24);
			encode(offset);
			encode(nv);
			encode(nv >>> 8);
			encode(nv >>> 16);
			encode(nv >>> 24);
		} else {
			throw new UnimplementedException();
		}
	}
	
	private void movl_imm4_off_ebp(int nv, int offset) {
		encodeSource("movl\t$"+ nv + ", " + offset + "(%ebp)");
		if (offset < 128) {
			encode(0xc7);
			encode(0x45);
			encode(offset);
			encode(nv);
			encode(nv >>> 8);
			encode(nv >>> 16);
			encode(nv >>> 24);
		} else {
			throw new UnimplementedException();
		}
	}

	private void movl_imm4_off_edx(int nv, int offset) {
		encodeSource("movl\t$"+ nv + ", " + offset + "(%edx)");
		if (offset >= -128 && offset < 128) {
			encode(0xc7);
			encode(0x42);
			encode(offset);
			encode(nv);
			encode(nv >>> 8);
			encode(nv >>> 16);
			encode(nv >>> 24);
		} else {
			throw new UnimplementedException();
		}
	}

	private void movl_eax_off_esp(int offset) {
		encodeSource("movl\t%eax, " + offset + "(%esp)");
		if (offset == 0) {
			encode(0x89);
			encode(0x04);
			encode(0x24);
		} else if (offset >= -128 && offset < 128) {
			encode(0x89);
			encode(0x44);
			encode(0x24);
			encode(offset);
		} else {
			throw new UnimplementedException();
		}
	}

	private void movl_eax_off_ebp(int offset) {
		encodeSource("movl\t%eax, " + offset + "(%ebp)");
		if (offset < 128) {
			encode(0x89);
			encode(0x45);
			encode(offset);
		} else {
			throw new UnimplementedException();
		}
	}

	private void movl_eax_off_edx(int offset) {
		encodeSource("movl\t%eax, " + offset + "(%edx)");
		if (offset < 128) {
			encode(0x89);
			encode(0x42);
			encode(offset);
		} else {
			throw new UnimplementedException();
		}
	}
	
	private void movl_edx_off_esp(int offset) {
		encodeSource("movl\t%edx, " + offset + "(%esp)");
		if (offset == 0) {
			encode(0x89);
			encode(0x14);
			encode(0x24);
		} else if (offset < 128) {
			encode(0x89);
			encode(0x54);
			encode(0x24);
			encode(offset);
		} else {
			throw new UnimplementedException();
		}
	}
	
	private void compileParam(int offset, Object v) {
		if (isImmediateValue(v)) {
			int nv = immediateValue(v);
			movl_imm4_off_esp(nv, offset);
		} else {
			loadEAX(v);
			movl_eax_off_esp(offset);
		}
	}
	
	private void callIntrinsic(String fnname, int callFrameSize) {
		if (callFrameSize > maxCallFrame)
			maxCallFrame = callFrameSize;
		
		encodeSource("call\t_" + fnname);
		encode(0xe8);
		encode(0);
		encode(0);
		encode(0);
		encode(0);
		
		List<Integer> refs = linkMap.get(fnname);
		if (refs == null) {
			refs = new ArrayList<Integer>();
			linkMap.put(fnname, refs);
		}
		refs.add(codeStream.size());
	}
	
	public void callBinOp(Object target, String fnname, Object left,
			Object right) {
		// get context
		movl_off_ebp_eax(8);
		movl_eax_off_esp(0);
		compileParam(4, left);
		compileParam(8, right);
		callIntrinsic(fnname, 3);
		assignEAX(target);
	} 

	public void callSimpleFunction(int slot, String fnname) {
		movl_off_ebp_eax(8);
		movl_eax_off_esp(0);
		callIntrinsic(fnname, 1);
		assignEAX(new ValueLocal(slot));
	}

	public void callUnaryOp(Object target, String fnname, Object v) {
		movl_off_ebp_eax(8);
		movl_eax_off_esp(0);
		compileParam(4, v);
		callIntrinsic(fnname, 2);
		assignEAX(target);
	}

	public void callVoidRuntime(String fnname, Object param, Object param2) {
		// get context
		movl_off_ebp_eax(8);
		movl_eax_off_esp(0);
		compileParam(4, param);
		compileParam(8, param2);
		callIntrinsic(fnname, 2);
	}

	public void invokeGet(Object target, Object obj, String field) {
		// get context
		movl_off_ebp_eax(8);
		movl_eax_off_esp(0);
		compileParam(4, obj);
		movl_imm4_off_esp(hash(field), 8);
		loadEAX(new ValueStringConstant(field));
		movl_eax_off_esp(12);

		callIntrinsic("jsgett", 4);
		assignEAX(target);
	}

	public void invokeGetAttr(Object target, Object obj, String field) {
		// get context
		movl_off_ebp_eax(8);
		movl_eax_off_esp(0);
		compileParam(4, obj);
		movl_imm4_off_esp(hash(field), 8);
		loadEAX(new ValueStringConstant(field));
		movl_eax_off_esp(12);
		callIntrinsic("jsgetattrt", 4);
		assignEAX(target);
	}

	public void invokeGetDesc(Object target, Object obj, String field) {
		// get context
		movl_off_ebp_eax(8);
		movl_eax_off_esp(0);
		compileParam(4, obj);
		movl_imm4_off_esp(hash(field), 8);
		loadEAX(new ValueStringConstant(field));
		movl_eax_off_esp(12);
		callIntrinsic("jsgetdesct", 4);
		assignEAX(target);
	}

	public void invokeGetElem(Object target, Object obj, Object index) {
		// get context
		movl_off_ebp_eax(8);
		movl_eax_off_esp(0);
		compileParam(4, obj);
		compileParam(8, index);
		callIntrinsic("jsgetelem", 4);
		assignEAX(target);
	}

	public void invokeInvoke(Object target, Object fn, Object thisValue,
			ArrayList<Object> params) {
		// get context
		movl_off_ebp_eax(8);
		movl_eax_off_esp(0);
		compileParam(4, fn);
		compileParam(8, thisValue);
		
		movl_off_ebp_eax(-12);
		movl_off_eax_edx(4);
		movl_edx_off_esp(12);
		movl_imm4_off_esp(params.size(), 16);
		
		for (int idx = 0; idx < params.size(); idx++) {
			Object v = params.get(idx);
			if (isImmediateValue(v)) {
				movl_imm4_off_edx(immediateValue(v), idx * 4);
			} else {
				loadEAX(v);
				movl_eax_off_edx(idx * 4);
			}
		}
		
		callIntrinsic("jsinvoke", 5);
		assignEAX(target);
	}

	public void invokeNew(Object target, Object classParam,
			ArrayList<Object> params) {
		// get context
		movl_off_ebp_eax(8);
		movl_eax_off_esp(0);
		compileParam(4, classParam);
		
		movl_off_ebp_eax(-12);
		movl_off_eax_edx(4);
		movl_edx_off_esp(8);

		for (int idx = 0; idx < params.size(); idx++) {
			Object v = params.get(idx);
			if (isImmediateValue(v)) {
				movl_imm4_off_edx(immediateValue(v), idx * 4);
			} else {
				loadEAX(v);
				movl_eax_off_edx(idx * 4);
			}
		}

		
		
		movl_imm4_off_esp(params.size(), 12);
		
		callIntrinsic("jsnew", 4);
		assignEAX(target);
	}

	public void invokeSet(Object obj, String field, Object v) {
		// get context
		movl_off_ebp_eax(8);
		movl_eax_off_esp(0);
		compileParam(4, obj);
		movl_imm4_off_esp(hash(field), 8);
		loadEAX(new ValueStringConstant(field));
		movl_eax_off_esp(12);
		compileParam(16, v);
		callIntrinsic("jssett", 5);
	}

	public void invokeSetElem(Object obj, Object index, Object v) {
		// get context
		movl_off_ebp_eax(8);
		movl_eax_off_esp(0);
		compileParam(4, obj);
		compileParam(8, index);
		compileParam(12, v);
		callIntrinsic("jssetelem", 4);
	}

	public void assignClosure(Object target, String fnname) {
		movl_off_ebp_eax(CONTEXT_EBP_OFFSET);
		movl_eax_off_esp(0);

		movl_imm4_off_esp(0, 4);
		
		codeReferences.add(new CodeRef("code_" + fnname, codeStream.size()));
		
		movl_off_ebp_eax(FRAME_EBP_OFFSET);
		movl_eax_off_esp(8);
		
		callIntrinsic("jsclosure", 3);
		assignEAX(target);
	}

	private void mergeLinkMap(HashMap<String, List<Integer>> map, int delta) {
		for (String fn : map.keySet()) {
			List<Integer> refs = map.get(fn);
			List<Integer> globRefs = globalLinkMap.get(fn);
			if (globRefs == null) {
				globRefs = new ArrayList<Integer>();
				globalLinkMap.put(fn, globRefs);
			}
			for (int idx = 0; idx < refs.size(); idx++) {
				globRefs.add(refs.get(idx) + delta);
			}
		}
	}
	
	private void writeInt(ByteArrayOutputStream out, int v) {
		out.write((byte) (v & 0xff));
		out.write((byte) ((v >> 8) & 0xff));
		out.write((byte) ((v >> 16) & 0xff));
		out.write((byte) ((v >> 24) & 0xff));
	}
	
	private void writeLong(ByteArrayOutputStream out, long v) {
		out.write((byte) (v & 0xff));
		out.write((byte) ((v >> 8) & 0xff));
		out.write((byte) ((v >> 16) & 0xff));
		out.write((byte) ((v >> 24) & 0xff));
		out.write((byte) ((v >> 32) & 0xff));
		out.write((byte) ((v >> 40) & 0xff));
		out.write((byte) ((v >> 48) & 0xff));
		out.write((byte) ((v >> 56) & 0xff));
	}
	
	private void writeString(ByteArrayOutputStream out, String v) {
		try {
			byte[] data = v.getBytes("UTF8");
			writeInt(out, data.length);
			out.write(data);
		} catch (UnsupportedEncodingException ex) {
			throw new RuntimeException(ex);
		} catch (IOException ex) {
			throw new RuntimeException(ex);
		}
	}
	
	private void writeStringConstant(ByteArrayOutputStream out, String v) {
		int len = v.length();
		
		writeInt(out, len);
		for (int idx = 0; idx < len; idx++) {
			int ch = v.charAt(idx);
			out.write((byte) (ch & 0xff));
			out.write((byte) ((ch >> 8) & 0xff));
		}
	}
	
	public byte[] getLinkMap() {
		ByteArrayOutputStream linkMap = new ByteArrayOutputStream();
		
//		System.out.println();

//		if (functionMap.size() > 0)
//			System.out.println("Function constants:");
		
		if (enableDebugging) {
			linkMap.write(103);
			writeInt(linkMap, nextGlobalId);
			writeInt(linkMap, breakpointLines.size());
			int pos = 0;
			while (pos < breakpointLines.size()) {
				String scr = breakpointScripts.get(pos);
				writeString(linkMap, scr);
				int base = pos;
				while (pos < breakpointLines.size()) {
					if (scr.equals(breakpointScripts.get(pos)))
						pos++;
					else
						break;
				}
				writeInt(linkMap, pos - base);
				for (int idx = base; idx < pos; idx++)
					writeInt(linkMap, breakpointLines.get(idx));
			}
		} else {
			linkMap.write(100);
			writeInt(linkMap, nextGlobalId);
		}

		for (int idx = 0; idx < functionMap.size(); idx += 2) {
			linkMap.write(1);
			int key, offset;
			key = functionMap.get(idx);
			offset = functionMap.get(idx + 1);
			writeInt(linkMap, key);
			writeInt(linkMap, offset);
//			System.out.println(functionMap.get(idx) + ": "  + functionMap.get(idx + 1));
		}
		
		linkMap.write(5);
		writeInt(linkMap, allCodeReferences.size());
		for (CodeRef ref : allCodeReferences) {
			writeInt(linkMap, ref.offset);
		}
		
//		System.out.println("Link map:");
		
		
		for (String fn : globalLinkMap.keySet()) {
//			System.out.println("Links for " + fn);
			linkMap.write(2);
			writeString(linkMap, fn);
			List<Integer> refs = globalLinkMap.get(fn);
			for (int idx = 0; idx < refs.size(); idx++) {
//				System.out.println("  " + refs.get(idx));
				writeInt(linkMap, refs.get(idx));
			}
			writeInt(linkMap, 0);
		}
		
		for (int idx = 0; idx < imageNames.size(); idx++) {
			linkMap.write(102);
			writeInt(linkMap, imageOffsets.get(idx));
			writeString(linkMap, imageNames.get(idx));
		}
		
		if (doubleConstantMap.size() > 0) {
			System.out.println("Double constants:");
			for (Entry<Double, Integer> e : doubleConstantMap.entrySet()) {
				linkMap.write(3);
				double dv = e.getKey();
				int off = e.getValue();
				writeInt(linkMap, off);
				long bits = Double.doubleToRawLongBits(dv);
				writeLong(linkMap, bits);
				System.out.println(e.getValue() + ": " + e.getKey());
			}
		}

		if (stringConstantMap.size() > 0) {
//			System.out.println("String constants:");
			for (Entry<String, Integer> e : stringConstantMap.entrySet()) {
				linkMap.write(4);
				String sv = e.getKey();
				int off = e.getValue();
				writeInt(linkMap, off);
				writeStringConstant(linkMap, sv);
//				System.out.println(e.getValue() + ": " + e.getKey());
			}
		}
		
		for (Integer entry : coreEntryPoints) {
			linkMap.write(101);
			writeInt(linkMap, entry);
		}

		for (Integer entry : entryPoints) {
			linkMap.write(101);
			writeInt(linkMap, entry);
		}
		
		linkMap.write(0);

		byte[] linkData = linkMap.toByteArray();
/*
		for (int idx = 0; idx < linkData.length; idx++)
			System.out.print("0x" + Integer.toHexString(linkData[idx] & 0xff) + ",");
		System.out.println();
*/
		return linkData;
	}

	public void addImageRef(int globId, String name, String path) {
		imageNames.add(name);
		imageOffsets.add(globId);
	}

}
